package ru.simplemaps.map.provider;

import ru.simplemaps.map.elements.ElementType;
import ru.simplemaps.osmapi.common.BBox;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Главный объект, который управляет загрузкой данных.
 * Другие компоненты системы просят загрузить определенные данные с помощью запросов (LoadRequest).
 *
 * @author enaku_adm
 * @since 29.07.2010 17:27:25
 */
public final class LoadManager {
    public static final int MAX_THREAD_NUMBER = 5;
    public static final double NET_SIZE = 0.1;

    private MapProvider mapProvider;

    public void setMapProvider(MapProvider mapProvider) {
        this.mapProvider = mapProvider;
    }

    /**
     * Очередь задач загрузок
     */
    private ConcurrentLinkedQueue<LoadTask> tasks = new ConcurrentLinkedQueue<LoadTask>();

    /**
     * Инициирует загрузку данных
     *
     * @param req запрос на загрузку данных
     */
    public void registerLoadRequest(LoadRequest req) {
        Collection<BBox> toLoad = req.box.simplify(NET_SIZE);
        for (ElementType type : req.types) {
            for (BBox bBox : toLoad) {
                tasks.add(new LoadTask(req, bBox, type));
            }
        }

        tryStartNext();
    }

    private Set<Thread> executing = Collections.synchronizedSet(new HashSet<Thread>());

    /**
     * Инициировать загрузку данных
     *
     * @param task задача, которую необходимо выполнить
     */
    private void initiateTask(final LoadTask task) {
        new Thread() {
            @SuppressWarnings({"unchecked"})
            @Override
            public void run() {
                try {
                    executing.add(this);
                    Collection collection = mapProvider.getProviders().get(task.type).load(task.box);
                    task.req.listener.onLoad(50, task.box, collection, task.type); //todo считать процент выполнения задачи
                } catch (Exception e) {
                    task.req.listener.onFailure(e);
                } finally {
                    threadFinished(this);
                }
            }
        }.start();
    }

    private void threadFinished(Thread thread) {
        executing.remove(thread);
        tryStartNext();
    }

    /**
     * Попробовать начать выполнение новой задачи
     */
    private void tryStartNext() {
        if (executing.size() < MAX_THREAD_NUMBER) {
            LoadTask task = tasks.poll();
            if (task != null) {
                initiateTask(task);
            }
        }
    }
}
